package com.xdb.sqlparser.dialect.oracle.parser;

import static com.xdb.sqlparser.parser.SQLCharTypes.isIdentifierChar;
import static com.xdb.sqlparser.parser.SQLLayoutCharacters.EOI;

import java.util.HashMap;
import java.util.Map;

import com.xdb.sqlparser.exception.NotAllowCommentException;
import com.xdb.sqlparser.exception.ParserException;
import com.xdb.sqlparser.parser.SQLKeywords;
import com.xdb.sqlparser.parser.SQLLexer;
import com.xdb.sqlparser.parser.SQLToken;

public class OracleLexer extends SQLLexer {

	public final static SQLKeywords DEFAULT_ORACLE_KEYWORDS;

	static {
		Map<String, SQLToken> map = new HashMap<String, SQLToken>();

		map.putAll(SQLKeywords.DEFAULT_KEYWORDS.getKeywords());

		map.put("BEGIN", SQLToken.BEGIN);
		map.put("COMMENT", SQLToken.COMMENT);
		map.put("COMMIT", SQLToken.COMMIT);
		map.put("CONNECT", SQLToken.CONNECT);

		map.put("CROSS", SQLToken.CROSS);
		map.put("CURSOR", SQLToken.CURSOR);
		map.put("DECLARE", SQLToken.DECLARE);
		map.put("ERRORS", SQLToken.ERRORS);
		map.put("EXCEPTION", SQLToken.EXCEPTION);

		map.put("EXCLUSIVE", SQLToken.EXCLUSIVE);
		map.put("EXTRACT", SQLToken.EXTRACT);
		map.put("GOTO", SQLToken.GOTO);
		map.put("IF", SQLToken.IF);

		map.put("LIMIT", SQLToken.LIMIT);
		map.put("LOOP", SQLToken.LOOP);
		map.put("MATCHED", SQLToken.MATCHED);
		map.put("MERGE", SQLToken.MERGE);

		map.put("MODE", SQLToken.MODE);
		map.put("MODEL", SQLToken.MODEL);
		map.put("NOWAIT", SQLToken.NOWAIT);
		map.put("OF", SQLToken.OF);
		map.put("PRIOR", SQLToken.PRIOR);

		map.put("REJECT", SQLToken.REJECT);
		map.put("RETURNING", SQLToken.RETURNING);
		map.put("SAVEPOINT", SQLToken.SAVEPOINT);
		map.put("SESSION", SQLToken.SESSION);

		map.put("SHARE", SQLToken.SHARE);
		map.put("START", SQLToken.START);
		map.put("SYSDATE", SQLToken.SYSDATE);
		map.put("UNLIMITED", SQLToken.UNLIMITED);
		map.put("USING", SQLToken.USING);

		map.put("WAIT", SQLToken.WAIT);
		map.put("WITH", SQLToken.WITH);

		map.put("IDENTIFIED", SQLToken.IDENTIFIED);

		map.put("PCTFREE", SQLToken.PCTFREE);
		map.put("INITRANS", SQLToken.INITRANS);
		map.put("MAXTRANS", SQLToken.MAXTRANS);
		map.put("SEGMENT", SQLToken.SEGMENT);
		map.put("CREATION", SQLToken.CREATION);
		map.put("IMMEDIATE", SQLToken.IMMEDIATE);
		map.put("DEFERRED", SQLToken.DEFERRED);
		map.put("STORAGE", SQLToken.STORAGE);
		map.put("NEXT", SQLToken.NEXT);
		map.put("MINEXTENTS", SQLToken.MINEXTENTS);
		map.put("MAXEXTENTS", SQLToken.MAXEXTENTS);
		map.put("MAXSIZE", SQLToken.MAXSIZE);
		map.put("PCTINCREASE", SQLToken.PCTINCREASE);
		map.put("FLASH_CACHE", SQLToken.FLASH_CACHE);
		map.put("CELL_FLASH_CACHE", SQLToken.CELL_FLASH_CACHE);
		map.put("KEEP", SQLToken.KEEP);
		map.put("NONE", SQLToken.NONE);
		map.put("LOB", SQLToken.LOB);
		map.put("STORE", SQLToken.STORE);
		map.put("ROW", SQLToken.ROW);
		map.put("CHUNK", SQLToken.CHUNK);
		map.put("CACHE", SQLToken.CACHE);
		map.put("NOCACHE", SQLToken.NOCACHE);
		map.put("LOGGING", SQLToken.LOGGING);
		map.put("NOCOMPRESS", SQLToken.NOCOMPRESS);
		map.put("KEEP_DUPLICATES", SQLToken.KEEP_DUPLICATES);
		map.put("EXCEPTIONS", SQLToken.EXCEPTIONS);
		map.put("PURGE", SQLToken.PURGE);
		map.put("INITIALLY", SQLToken.INITIALLY);

		DEFAULT_ORACLE_KEYWORDS = new SQLKeywords(map);
	}

	public OracleLexer(char[] input, int inputLength, boolean skipComment) {
		super(input, inputLength, skipComment);
		super.keywods = DEFAULT_ORACLE_KEYWORDS;
	}

	public OracleLexer(String input) {
		super(input);
		super.keywods = DEFAULT_ORACLE_KEYWORDS;
	}

	public void scanVariable() {
		if (ch == '@') {
			scanChar();
			token = SQLToken.MONKEYS_AT;
			return;
		}

		if (ch != ':' && ch != '#' && ch != '$') {
			throw new ParserException("illegal variable");
		}

		mark = pos;
		bufPos = 1;
		char ch;

		boolean quoteFlag = false;
		boolean mybatisFlag = false;
		if (charAt(pos + 1) == '"') {
			pos++;
			bufPos++;
			quoteFlag = true;
		} else if (charAt(pos + 1) == '{') {
			pos++;
			bufPos++;
			mybatisFlag = true;
		}

		for (;;) {
			ch = charAt(++pos);

			if (!isIdentifierChar(ch)) {
				break;
			}

			bufPos++;
			continue;
		}

		if (quoteFlag) {
			if (ch != '"') {
				throw new ParserException("syntax error");
			}
			++pos;
			bufPos++;
		} else if (mybatisFlag) {
			if (ch != '}') {
				throw new ParserException("syntax error");
			}
			++pos;
			bufPos++;
		}

		this.ch = charAt(pos);

		stringVal = addSymbol();
		SQLToken tok = keywods.getKeyword(stringVal);
		if (tok != null) {
			token = tok;
		} else {
			token = SQLToken.VARIANT;
		}
	}

	public void scanComment() {
		if (ch != '/' && ch != '-') {
			throw new IllegalStateException();
		}

		mark = pos;
		bufPos = 0;
		scanChar();

		// /*+ */
		if (ch == '*') {
			scanChar();
			bufPos++;

			while (ch == ' ') {
				scanChar();
				bufPos++;
			}

			boolean isHint = false;
			int startHintSp = bufPos + 1;
			if (ch == '+') {
				isHint = true;
				scanChar();
				bufPos++;
			}

			for (;;) {
				if (ch == '*' && charAt(pos + 1) == '/') {
					bufPos += 2;
					scanChar();
					scanChar();
					break;
				}

				scanChar();
				bufPos++;
			}

			if (isHint) {
				stringVal = subString(mark + startHintSp, (bufPos - startHintSp) - 1);
				token = SQLToken.HINT;
			} else {
				stringVal = subString(mark, bufPos);
				token = SQLToken.MULTI_LINE_COMMENT;
				hasComment = true;
			}

			if (token != SQLToken.HINT && !isAllowComment()) {
				throw new NotAllowCommentException();
			}

			return;
		}

		if (!isAllowComment()) {
			throw new NotAllowCommentException();
		}

		if (ch == '/' || ch == '-') {
			scanChar();
			bufPos++;

			for (;;) {
				if (ch == '\r') {
					if (charAt(pos + 1) == '\n') {
						bufPos += 2;
						scanChar();
						break;
					}
					bufPos++;
					break;
				} else if (ch == EOI) {
					break;
				}

				if (ch == '\n') {
					scanChar();
					bufPos++;
					break;
				}

				scanChar();
				bufPos++;
			}

			stringVal = subString(mark + 1, bufPos);
			token = SQLToken.LINE_COMMENT;
			hasComment = true;
			endOfComment = isEOF();
			return;
		}
	}

	public void scanNumber() {
		mark = pos;

		if (ch == '-') {
			bufPos++;
			ch = charAt(++pos);
		}

		for (;;) {
			if (ch >= '0' && ch <= '9') {
				bufPos++;
			} else {
				break;
			}
			ch = charAt(++pos);
		}

		boolean isDouble = false;

		if (ch == '.') {
			if (charAt(pos + 1) == '.') {
				token = SQLToken.LITERAL_INT;
				return;
			}
			bufPos++;
			ch = charAt(++pos);
			isDouble = true;

			for (;;) {
				if (ch >= '0' && ch <= '9') {
					bufPos++;
				} else {
					break;
				}
				ch = charAt(++pos);
			}
		}

		if (ch == 'e' || ch == 'E') {
			bufPos++;
			ch = charAt(++pos);

			if (ch == '+' || ch == '-') {
				bufPos++;
				ch = charAt(++pos);
			}

			for (;;) {
				if (ch >= '0' && ch <= '9') {
					bufPos++;
				} else {
					break;
				}
				ch = charAt(++pos);
			}

			isDouble = true;
		}

		if (ch == 'f' || ch == 'F') {
			token = SQLToken.BINARY_FLOAT;
			scanChar();
			return;
		}

		if (ch == 'd' || ch == 'D') {
			token = SQLToken.BINARY_DOUBLE;
			scanChar();
			return;
		}

		if (isDouble) {
			token = SQLToken.LITERAL_FLOAT;
		} else {
			token = SQLToken.LITERAL_INT;
		}
	}

}
